#!/usr/bin/python3
# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t -*-

import os
import sys
import json
import glob
import time
import shutil
import socket
import subprocess
import atomicwrites
import http.client
import urllib.error
import urllib.request
import mirrors.plugin


class Main:

    def __init__(self, sock):
        self.sock = sock
        self.dataDir = mirrors.plugin.params["storage-file"]["data-directory"]
        self.p = InfoPrinter()
        self.progress = 0

    def run(self):
        self._downloadXkcd(20)
        self._downloadWhatIf(40)
        self._downloadExplainXkcd(100)

    def _downloadXkcd(self, endProgress):
        self.p.print("Downloading comic")
        self.p.incIndent()
        try:
            baseUrl = "https://xkcd.com/"
            imageUrl = "https://imgs.xkcd.com/comics/"

            lastNumber = None
            if True:
                ret = self.__getJson(baseUrl + "info.0.json")
                lastNumber = ret['num']

            for i in range(1, lastNumber + 1):
                self.p.print("Processing comic %d" % (i))
                self.p.incIndent()
                try:
                    # xkcd comic jumps over 404, haha
                    if i == 404:
                        self.p.print("Ignore. It's a real 404 page. :D")
                        continue

                    # FIXME: comic-1608 is a game
                    if i == 1608:
                        self.p.print("Ignore. FIXME")
                        continue

                    # FIXME: comic-1663 is a game
                    if i == 1663:
                        self.p.print("Ignore. FIXME")
                        continue

                    curDir = os.path.join(self.dataDir, "xkcd-" + str(i))
                    jsonFullfn = os.path.join(curDir, "info.0.json")
                    Util.forceMakeDir(curDir)

                    # already downloaded?
                    if os.path.exists(jsonFullfn):
                        self.p.print("Already downloaded.")
                        continue

                    # do download
                    self.p.print("Downloading...")
                    Util.forceClearDir(curDir)

                    # download json
                    jsonData = self.__getJson(baseUrl + os.path.join(str(i), "info.0.json"))
                    if not jsonData["img"].startswith(imageUrl) or len(jsonData["img"]) <= len(imageUrl):
                        raise Exception("invalid value for key \"img\": %s" % (jsonData["img"]))

                    # download image
                    imageName = jsonData["img"][len(imageUrl):]
                    imageFullfn = os.path.join(curDir, imageName)
                    self.__downloadFile(jsonData["img"], imageFullfn)

                    # write json file
                    with atomicwrites.atomic_write(jsonFullfn) as f:
                        json.dump(jsonData, f)
                finally:
                    self.p.decIndent()
        finally:
            self.p.decIndent()

        self.sock.progress_changed(endProgress)

    def _downloadWhatIf(self, endProgress):
        self.p.print("Downloading what-if")
        self.p.incIndent()
        try:
            archiveUrl = "https://what-if.xkcd.com/archive/"

            lastNumber = None
            if True:
                archive = urllib.request.urlopen(archiveUrl)
                text = archive.read().decode('utf-8')
                archive.close()
                lastNumber = 1

            for i in range(1, lastNumber + 1):
                self.p.print("Processing what-if %d" % (i))
                self.p.incIndent()
                try:
                    curDir = os.path.join(self.dataDir, "whatif-" + str(i))
                    fullfn = os.path.join(curDir, "page.mht")
                    Util.forceMakeDir(curDir)

                    # already downloaded?
                    if os.path.exists(fullfn):
                        self.p.print("Already downloaded.")
                        continue

                    # do download
                    self.p.print("Downloading...")
                    Util.forceClearDir(curDir)

                    # FIXME
                    pass
                finally:
                    self.p.decIndent()
        finally:
            self.p.decIndent()

        self.sock.progress_changed(endProgress)

    def _downloadExplainXkcd(self, endProgress):
        self.p.print("Downloading explain-xkcd")
        self.p.incIndent()
        try:
            baseUrl = "https://explainxkcd.com/"

            for curDir in glob.glob(os.path.join(self.dataDir, "xkcd-*")):
                i = int(os.path.basename(curDir)[len("xkcd-"):])
                self.p.print("Processing explain-xkcd %d" % (i))
                self.p.incIndent()
                try:
                    url = baseUrl + str(i)
                    fullfn = os.path.join(curDir, "explain.mht")

                    # already downloaded?
                    if os.path.exists(fullfn):
                        self.p.print("Already downloaded.")
                        continue

                    # do download
                    self.p.print("Downloading...")

                    # FIXME
                    pass
                finally:
                    self.p.decIndent()
        finally:
            self.p.decIndent()

        self.sock.progress_changed(endProgress)

    def __getJson(self, url):
        while True:
            try:
                resp = urllib.request.urlopen(url)
                return json.load(resp)
            except socket.timeout as e:
                self.p.print("urlopen failed and try again: %s" % str(e))
                time.sleep(10.0)
            except http.client.HTTPException as e:
                self.p.print("urlopen failed and try again: %s" % str(e))
                time.sleep(10.0)
            except urllib.error.HTTPError as e:
                if e.code == 404:
                    raise
                self.p.print("urlopen failed and try again: %s" % str(e))
                time.sleep(10.0)
            except urllib.error.URLError as e:
                self.p.print("urlopen failed and try again: %s" % str(e))
                time.sleep(10.0)

    def __downloadFile(self, url, localFile):
        while True:
            try:
                subprocess.check_call(["wget", "--quiet", "--no-check-certificate", "-O", localFile, url])      # always re-dowloand
                break
            except subprocess.CalledProcessError as e:
                if e.returncode == 8:       # not found
                    raise
                self.p.print("download failed and try again: %s" % str(e))
                time.sleep(60)


class InfoPrinter:

    def __init__(self):
        self.indent = 0

    def incIndent(self):
        self.indent = self.indent + 1

    def decIndent(self):
        assert self.indent > 0
        self.indent = self.indent - 1

    def print(self, s):
        line = ""
        line += "\t" * self.indent
        line += s
        print(line)


class Util:

    @staticmethod
    def forceDelete(path):
        if os.path.islink(path):
            os.remove(path)
        elif os.path.isfile(path):
            os.remove(path)
        elif os.path.isdir(path):
            shutil.rmtree(path)
        elif os.path.lexists(path):
            os.remove(path)             # other type of file, such as device node
        else:
            pass                        # path does not exist, do nothing

    @staticmethod
    def forceMakeDir(path):
        if not os.path.exists(path):
            os.makedirs(path)
        elif not os.path.isdir(path):
            Util.forceDelete(path)
            os.mkdir(path)
        else:
            pass

    @staticmethod
    def forceClearDir(path):
        for fn in os.listdir(path):
            Util.forceDelete(os.path.join(path, fn))


###############################################################################

if __name__ == "__main__":
    with mirrors.plugin.ApiClient() as sock:
        try:
            Main(sock).run()
            sock.progress_changed(100)
        except Exception:
            sock.error_occured(sys.exc_info())
            raise
